package codegen

import (
	"fmt"
	"strings"

	"goa.design/goa/v3/codegen"
	"goa.design/goa/v3/codegen/service"
	"goa.design/goa/v3/expr"
)

type (
	// ServicesData contains the data computed from the gRPC service expressions
	// indexed by service name.
	ServicesData struct {
		*service.ServicesData
		GRPCServices map[string]*ServiceData
	}

	// ServiceData contains the data used to render the code related to a
	// single service.
	ServiceData struct {
		// Service contains the related service data.
		Service *service.Data
		// PkgName is the name of the generated package in *.pb.go.
		PkgName string
		// ProtoImports is the list of proto package imports.
		ProtoImports []string
		// Name is the service name.
		Name string
		// Description is the service description.
		Description string
		// Endpoints describes the gRPC service endpoints.
		Endpoints []*EndpointData
		// Messages describes the message data for this service.
		Messages []*service.UserTypeData
		// ServerStruct is the name of the gRPC server struct.
		ServerStruct string
		// ClientStruct is the name of the gRPC client struct,
		ClientStruct string
		// ServerInit is the name of the constructor of the server struct.
		ServerInit string
		// ClientInit is the name of the constructor of the client struct.
		ClientInit string
		// ServerInterface is the name of the gRPC server interface implemented
		// by the service.
		ServerInterface string
		// ClientInterface is the name of the gRPC client interface implemented
		// by the service.
		ClientInterface string
		// ClientInterfaceInit is the name of the client constructor function in
		// the generated pb.go package.
		ClientInterfaceInit string
		// Scope is the name scope for protocol buffers
		Scope *codegen.NameScope

		// transformHelpers is the list of transform functions required by the
		// constructors.
		transformHelpers []*codegen.TransformFunctionData
		// validations contain the data to generate the validation functions to
		// validate the initialized type.
		validations []*ValidationData
	}

	// EndpointData contains the data used to render the code related to
	// gRPC endpoint.
	EndpointData struct {
		// ServiceName is the name of the service.
		ServiceName string
		// PkgName is the name of the generated package in *.pb.go.
		PkgName string
		// ServicePkgName is the name of the service package name.
		ServicePkgName string
		// Method is the data for the underlying method expression.
		Method *service.MethodData
		// PayloadType is the type of the payload.
		PayloadType expr.DataType
		// PayloadRef is the fully qualified reference to the method payload.
		PayloadRef string
		// ResultRef is the fully qualified reference to the method result.
		ResultRef string
		// ViewedResultRef is the fully qualified reference to the viewed result.
		ViewedResultRef string
		// Request is the gRPC request data.
		Request *RequestData
		// Response is the gRPC response data.
		Response *ResponseData
		// MetadataSchemes lists all the security requirement schemes that
		// apply to the method and are encoded in the request metadata.
		MetadataSchemes service.SchemesData
		// MessageSchemes lists all the security requirement schemes that
		// apply to the method and are encoded in the request message.
		MessageSchemes service.SchemesData
		// Errors describes the method gRPC errors.
		Errors []*ErrorData

		// server side

		// ServerStruct is the name of the gRPC server struct.
		ServerStruct string
		// ServerInterface is the name of the gRPC server interface implemented
		// by the service.
		ServerInterface string
		// ServerStream is the server stream data.
		ServerStream *StreamData

		// client side

		// ClientMethodName is the name of the gRPC method generated by protoc-gen-go.
		ClientMethodName string
		// ClientStruct is the name of the gRPC client struct,
		ClientStruct string
		// ClientInterface is the name of the gRPC client interface implemented
		// by the service.
		ClientInterface string
		// ClientStream is the client stream data.
		ClientStream *StreamData
	}

	// MetadataData describes a gRPC metadata field.
	MetadataData struct {
		// Name is the name of the metadata key.
		Name string
		// AttributeName is the name of the corresponding attribute.
		AttributeName string
		// Description is the metadata description.
		Description string
		// FieldName is the name of the struct field that holds the
		// metadata value if any, empty string otherwise.
		FieldName string
		// FieldType is the type of the struct field.
		FieldType expr.DataType
		// VarName is the name of the Go variable used to read or
		// convert the metadata value.
		VarName string
		// TypeName is the name of the type.
		TypeName string
		// TypeRef is the reference to the type.
		TypeRef string
		// Required is true if the metadata is required.
		Required bool
		// Pointer is true if and only the metadata variable is a pointer.
		Pointer bool
		// StringSlice is true if the metadata value type is array of strings.
		StringSlice bool
		// Slice is true if the metadata value type is an array.
		Slice bool
		// MapStringSlice is true if the metadata value type is a map of string
		// slice.
		MapStringSlice bool
		// Map is true if the metadata value type is a map.
		Map bool
		// Type describes the datatype of the variable value. Mainly
		// used for conversion.
		Type expr.DataType
		// Validate contains the validation code if any.
		Validate string
		// DefaultValue contains the default value if any.
		DefaultValue any
		// Example is an example value.
		Example any
	}

	// ErrorData contains the error information required to generate the
	// transport decode (client) and encode (server) code.
	ErrorData struct {
		// StatusCode is the response gRPC status code.
		StatusCode string
		// Name is the error name.
		Name string
		// Ref is a reference to the error type.
		Ref string
		// Response is the error response data.
		Response *ResponseData
	}

	// RequestData describes a gRPC request.
	RequestData struct {
		// Description is the request description.
		Description string
		// Message is the gRPC request message.
		Message *service.UserTypeData
		// Metadata is the request metadata.
		Metadata []*MetadataData
		// ServerConvert is the request data with constructor function to
		// initialize the method payload type from the generated payload type in
		// *.pb.go.
		ServerConvert *ConvertData
		// ClientConvert is the request data with constructor function to
		// initialize the generated payload type in *.pb.go from the
		// method payload.
		ClientConvert *ConvertData
		// CLIArgs is the list of arguments for the command-line client.
		// This is set only for the client side.
		CLIArgs []*InitArgData
	}

	// ResponseData describes a gRPC success or error response.
	ResponseData struct {
		// StatusCode is the return code of the response.
		StatusCode string
		// Description is the response description.
		Description string
		// Message is the gRPC response message.
		Message *service.UserTypeData
		// Headers is the response header metadata.
		Headers []*MetadataData
		// Trailers is the response trailer metadata.
		Trailers []*MetadataData
		// ServerConvert is the type data with constructor function to
		// initialize the generated response type in *.pb.go from the
		// method result type or the projected result type.
		ServerConvert *ConvertData
		// ClientConvert is the type data with constructor function to
		// initialize the method result type or the projected result type
		// from the generated response type in *.pb.go.
		ClientConvert *ConvertData
	}

	// ConvertData contains the data to convert source type to a target type.
	// For request type, it contains data to transform gRPC request type to the
	// corresponding payload type (server) and vice versa (client).
	// For response type, it contains data to transform gRPC response type to the
	// corresponding result type (client) and vice versa (server).
	ConvertData struct {
		// SrcName is the fully qualified name of the source type.
		SrcName string
		// SrcRef is the fully qualified reference to the source type.
		SrcRef string
		// TgtName is the fully qualified name of the target type.
		TgtName string
		// TgtRef is the fully qualified reference to the target type.
		TgtRef string
		// Inits contain the data required to render the constructor if any
		// to transform the source type to a target type. If the source or target
		// type is a goa result type, we generate one constructor for every view
		// defined in the result type.
		Init *InitData
		// Validation contains the data required to render the validation function
		// to validate the initialized type.
		Validation *ValidationData
	}

	// ValidationData contains the data necessary to render the validation
	// function.
	ValidationData struct {
		// Name is the validation function name.
		Name string
		// Def is the validation function definition.
		Def string
		// VarName is the name of the argument.
		ArgName string
		// SrcName is the fully qualified name of the type being validated.
		SrcName string
		// SrcRef is the fully qualified reference to the type being validated.
		SrcRef string
		// Kind indicates that the validation is for request (server-side),
		// response (client-side), or both (server and client side) messages.
		// It is used to generate validation code in the server and client packages.
		Kind validateKind
	}

	// InitData contains the data required to render a constructor.
	InitData struct {
		// Name is the constructor function name.
		Name string
		// Description is the function description.
		Description string
		// Args is the list of constructor arguments.
		Args []*InitArgData
		// ReturnVarName is the name of the variable to be returned.
		ReturnVarName string
		// ReturnTypeRef is the qualified (including the package name)
		// reference to the return type.
		ReturnTypeRef string
		// ReturnTypePkg is the package where the return type is present.
		ReturnTypePkg string
		// ReturnIsStruct is true if the return type is a struct.
		ReturnIsStruct bool
		// Code is the transformation code.
		Code string
	}

	// InitArgData represents a single constructor argument.
	InitArgData struct {
		// Name is the argument name.
		Name string
		// Description is the argument description.
		Description string
		// Reference to the argument, e.g. "&body".
		Ref string
		// FieldName is the name of the data structure field that should
		// be initialized with the argument if any.
		FieldName string
		// FieldType is the type of the data structure field that should be
		// initialized with the argument if any.
		FieldType expr.DataType
		// TypeName is the argument type name.
		TypeName string
		// TypeRef is the argument type reference.
		TypeRef string
		// Type is the argument type. It is never an aliased user type.
		Type expr.DataType
		// Pointer is true if a pointer to the arg should be used.
		Pointer bool
		// Required is true if the arg is required to build the payload.
		Required bool
		// DefaultValue is the default value of the arg.
		DefaultValue any
		// Validate contains the validation code for the argument
		// value if any.
		Validate string
		// Example is a example value
		Example any
	}

	// StreamData contains data to render the stream struct type that implements
	// the service stream interface.
	StreamData struct {
		// VarName is the name of the struct type.
		VarName string
		// Type is the stream type (client or server).
		Type string
		// ServiceInterface is the service interface that the struct implements.
		ServiceInterface string
		// Interface is the stream interface in *.pb.go stored in the struct.
		Interface string
		// Endpoint is the streaming endpoint data.
		Endpoint *EndpointData
		// SendName is the name of the send function.
		SendName string
		// SendDesc is the description for the send function.
		SendDesc string
		// SendWithContextName is the name of the send function with context.
		SendWithContextName string
		// SendWithContextDesc is the description for the send function with context.
		SendWithContextDesc string
		// SendRef is the fully	qualified reference to the type sent across the
		// stream.
		SendRef string
		// SendConvert is the type sent through the stream. It contains the
		// constructor to convert the service send type to the type expected by
		// the gRPC send type (in *.pb.go)
		SendConvert *ConvertData
		// RecvConvert is the type received through the stream. It contains the
		// constructor to convert the gRPC type (in *.pb.go) to the service receive
		// type.
		RecvConvert *ConvertData
		// RecvName is the name of the receive function.
		RecvName string
		// RecvDesc is the description for the recv function.
		RecvDesc string
		// RecvWithContextName is the name of the receive function with context.
		RecvWithContextName string
		// RecvWithContextDesc is the description for the recv function with context.
		RecvWithContextDesc string
		// RecvRef is the fully	qualified reference to the type received from the
		// stream.
		RecvRef string
		// MustClose indicates whether to generate the Close() function
		// for the stream.
		MustClose bool
	}

	// validateKind is a type to determine where the validation code is generated
	// (server, client, or both)
	validateKind int
)

// NewServicesData creates a new ServicesData instance for the given service data.
func NewServicesData(services *service.ServicesData) *ServicesData {
	return &ServicesData{
		ServicesData: services,
		GRPCServices: make(map[string]*ServiceData),
	}
}

const (
	// pbPkgName is the directory name where the .proto file is generated and
	// compiled.
	pbPkgName = "pb"
)

const (
	// validateServer generates the validation code for request messages in the
	// server package.
	validateServer validateKind = iota + 1
	// validateClient generates the validation code for response messages in the
	// client package.
	validateClient
	// validateBoth generates the validation code in both server and client
	// packages.
	validateBoth
)

// Get retrieves the transport data for the service with the given name
// computing it if needed. It returns nil if there is no service with the given
// name.
func (d *ServicesData) Get(name string) *ServiceData {
	if data, ok := d.GRPCServices[name]; ok {
		return data
	}
	service := d.Root.API.GRPC.Service(name)
	if service == nil {
		return nil
	}
	d.GRPCServices[name] = d.analyze(service)
	return d.GRPCServices[name]
}

// Endpoint returns the endpoint data for the endpoint with the given name, nil
// if there isn't one.
func (sd *ServiceData) Endpoint(name string) *EndpointData {
	for _, ed := range sd.Endpoints {
		if ed.Method.Name == name {
			return ed
		}
	}
	return nil
}

// HasUnaryEndpoint returns true if the service has at least one unary endpoint.
func (sd *ServiceData) HasUnaryEndpoint() bool {
	for _, ed := range sd.Endpoints {
		if ed.ServerStream == nil {
			return true
		}
	}
	return false
}

// HasStreamingEndpoint returns true if the service has at least one streaming
// endpoint.
func (sd *ServiceData) HasStreamingEndpoint() bool {
	for _, ed := range sd.Endpoints {
		if ed.ServerStream != nil {
			return true
		}
	}
	return false
}

// analyze creates the data necessary to render the code of the given service.
func (d *ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
	svc := d.ServicesData.Get(gs.Name())
	scope := codegen.NewNameScope()
	pkg := codegen.SnakeCase(codegen.Goify(svc.Name, false)) + pbPkgName
	svcVarN := scope.HashedUnique(gs.ServiceExpr, codegen.Goify(svc.Name, true))
	sd := &ServiceData{
		Service:             svc,
		Name:                svcVarN,
		Description:         svc.Description,
		PkgName:             pkg,
		ServerStruct:        "Server",
		ClientStruct:        "Client",
		ServerInit:          "New",
		ClientInit:          "NewClient",
		ServerInterface:     svcVarN + "Server",
		ClientInterface:     svcVarN + "Client",
		ClientInterfaceInit: fmt.Sprintf("%s.New%sClient", pkg, svcVarN),
		Scope:               scope,
	}
	seen, imported := make(map[string]struct{}), make(map[string]struct{})
	for _, e := range gs.GRPCEndpoints {
		// convert request and response types to protocol buffer message types
		e.Request = makeProtoBufMessage(e.Request, protoBufify(e.Name()+"_request", true, true), sd)
		if e.MethodExpr.StreamingPayload.Type != expr.Empty {
			e.StreamingRequest = makeProtoBufMessage(e.StreamingRequest, protoBufify(e.Name()+"_streaming_request", true, true), sd)
		}
		e.Response.Message = makeProtoBufMessage(e.Response.Message, protoBufify(e.Name()+"_response", true, true), sd)
		for _, er := range e.GRPCErrors {
			if er.Type == expr.ErrorResult || !expr.IsObject(er.Type) {
				continue
			}
			er.Response.Message = makeProtoBufMessage(er.Response.Message, protoBufify(e.Name()+"_"+er.Name+"_error", true, true), sd)
		}

		// collect all the nested messages and return the top-level message
		// Also collect all proto imports specified via Meta.
		collect := func(att *expr.AttributeExpr) *service.UserTypeData {
			msgs, imports := collectMessages(att, sd, seen)
			if len(imports) > 0 {
				for _, imp := range imports {
					if _, ok := imported[imp]; ok {
						continue
					}
					imported[imp] = struct{}{}
					sd.ProtoImports = append(sd.ProtoImports, imp)
				}
			}
			if len(msgs) > 0 {
				sd.Messages = append(sd.Messages, msgs...)
				return msgs[0]
			}
			// lookup message in sd.Messages
			if ut, ok := att.Type.(expr.UserType); ok {
				name := ut.Name()
				if n := att.Meta["struct:name:proto"]; n != nil {
					name = n[0]
				}
				for _, t := range sd.Messages {
					if t.Name == name {
						return t
					}
				}
			}
			return nil
		}

		var (
			payloadRef      string
			resultRef       string
			viewedResultRef string
		)
		md := svc.Method(e.Name())
		if e.MethodExpr.Payload.Type != expr.Empty {
			payloadRef = svc.Scope.GoFullTypeRef(e.MethodExpr.Payload,
				pkgWithDefault(md.PayloadLoc, svc.PkgName))
		}
		if e.MethodExpr.Result.Type != expr.Empty {
			resultRef = svc.Scope.GoFullTypeRef(e.MethodExpr.Result,
				pkgWithDefault(md.ResultLoc, svc.PkgName))
		}
		if md.ViewedResult != nil {
			viewedResultRef = md.ViewedResult.FullRef
		}
		errors := d.buildErrorsData(e, sd)
		for _, er := range e.GRPCErrors {
			if er.Type == expr.ErrorResult || !expr.IsObject(er.Type) {
				continue
			}
			collect(er.Response.Message)
		}

		// build request data
		reqMD := extractMetadata(e.Metadata, e.MethodExpr.Payload, svc.Scope, *d)
		request := &RequestData{
			Description:   e.Request.Description,
			Metadata:      reqMD,
			ServerConvert: d.buildRequestConvertData(e.Request, e.MethodExpr.Payload, reqMD, e, sd, true),
			ClientConvert: d.buildRequestConvertData(e.Request, e.MethodExpr.Payload, reqMD, e, sd, false),
		}
		if obj := expr.AsObject(e.Request.Type); (obj != nil && len(*obj) > 0) || expr.IsUnion(e.Request.Type) {
			// add the request message as the first argument to the CLI
			request.CLIArgs = append(request.CLIArgs, &InitArgData{
				Name:     "message",
				Ref:      "message",
				TypeName: protoBufGoFullTypeName(e.Request, sd.PkgName, sd.Scope),
				TypeRef:  protoBufGoFullTypeRef(e.Request, sd.PkgName, sd.Scope),
				Example:  e.Request.Example(d.Root.API.ExampleGenerator),
			})
		}
		// pass the metadata as arguments to client CLI args
		for _, m := range reqMD {
			request.CLIArgs = append(request.CLIArgs, &InitArgData{
				Name:         m.VarName,
				Ref:          m.VarName,
				FieldName:    m.FieldName,
				FieldType:    m.FieldType,
				TypeName:     m.TypeName,
				TypeRef:      m.TypeRef,
				Type:         m.Type,
				Pointer:      m.Pointer,
				Required:     m.Required,
				Validate:     m.Validate,
				Example:      m.Example,
				DefaultValue: m.DefaultValue,
			})
		}
		if e.StreamingRequest.Type != expr.Empty {
			request.Message = collect(e.StreamingRequest)
		} else {
			request.Message = collect(e.Request)
		}

		// build response data
		result, svcCtx := resultContext(e, sd)
		hdrs := extractMetadata(e.Response.Headers, result, svc.Scope, *d)
		trlrs := extractMetadata(e.Response.Trailers, result, svc.Scope, *d)
		response := &ResponseData{
			StatusCode:    statusCodeToGRPCConst(e.Response.StatusCode),
			Description:   e.Response.Description,
			Headers:       hdrs,
			Trailers:      trlrs,
			ServerConvert: d.buildResponseConvertData(e.Response.Message, result, svcCtx, hdrs, trlrs, e, sd, true),
			ClientConvert: d.buildResponseConvertData(e.Response.Message, result, svcCtx, hdrs, trlrs, e, sd, false),
		}
		// If the endpoint is a streaming endpoint, no message is returned
		// by gRPC. Hence, no need to set response message.
		if e.Response.Message.Type != expr.Empty || !e.MethodExpr.IsStreaming() {
			response.Message = collect(e.Response.Message)
		}

		// gather security requirements
		var (
			msgSch service.SchemesData
			metSch service.SchemesData
		)
		for _, req := range e.Requirements {
			for _, sch := range req.Schemes {
				s := md.Requirements.Scheme(sch.SchemeName).Dup()
				s.In = sch.In
				switch s.In {
				case "message":
					msgSch = msgSch.Append(s)
				default:
					metSch = metSch.Append(s)
				}
			}
		}
		ed := &EndpointData{
			ServiceName:      svc.Name,
			PkgName:          sd.PkgName,
			ServicePkgName:   svc.PkgName,
			Method:           md,
			PayloadType:      e.MethodExpr.Payload.Type,
			PayloadRef:       payloadRef,
			ResultRef:        resultRef,
			ViewedResultRef:  viewedResultRef,
			Request:          request,
			Response:         response,
			MessageSchemes:   msgSch,
			MetadataSchemes:  metSch,
			Errors:           errors,
			ServerStruct:     sd.ServerStruct,
			ServerInterface:  sd.ServerInterface,
			ClientMethodName: protoBufify(md.VarName, true, true),
			ClientStruct:     sd.ClientStruct,
			ClientInterface:  sd.ClientInterface,
		}
		sd.Endpoints = append(sd.Endpoints, ed)
		if e.MethodExpr.IsStreaming() {
			ed.ServerStream = d.buildStreamData(e, sd, true)
			ed.ClientStream = d.buildStreamData(e, sd, false)
		}
	}
	return sd
}

// collectMessages recurses through the attribute to gather all the messages.
func collectMessages(at *expr.AttributeExpr, sd *ServiceData, seen map[string]struct{}) (data []*service.UserTypeData, imports []string) {
	if at == nil {
		return data, imports
	}
	if proto := at.Meta["struct:field:proto"]; len(proto) > 1 {
		if len(proto) > 1 {
			imp := proto[1]
			found := false
			for _, i := range sd.Service.ProtoImports {
				if i.Path == imp {
					found = true
					break
				}
			}
			if !found {
				imports = append(imports, imp)
				if len(proto) > 3 {
					elems := strings.Split(proto[3], "/")
					sd.Service.ProtoImports = append(sd.Service.ProtoImports, &codegen.ImportSpec{Path: proto[3], Name: elems[len(elems)-1]})
				}
			}
		}
	}
	if expr.IsPrimitive(at.Type) {
		// Add google.protobuf.Any import when Any type is used
		if at.Type.Kind() == expr.AnyKind {
			found := false
			for _, imp := range imports {
				if imp == "google/protobuf/any.proto" {
					found = true
					break
				}
			}
			if !found {
				imports = append(imports, "google/protobuf/any.proto")
			}
		}
		return data, imports
	}
	collect := func(at *expr.AttributeExpr) ([]*service.UserTypeData, []string) {
		return collectMessages(at, sd, seen)
	}
	switch dt := at.Type.(type) {
	case expr.UserType:
		name := dt.Name()
		if n := at.Meta["struct:name:proto"]; n != nil {
			name = n[0]
		}
		if _, ok := seen[name]; ok {
			return data, imports
		}
		att := userTypeAttribute(dt)
		data = append(data, &service.UserTypeData{
			Name:        name,
			VarName:     protoBufMessageName(at, sd.Scope),
			Description: dt.Attribute().Description,
			Def:         protoBufMessageDef(att, sd),
			Ref:         protoBufGoFullTypeRef(at, sd.PkgName, sd.Scope),
			Type:        dt,
		})
		seen[name] = struct{}{}
		d, i := collect(att)
		data = append(data, d...)
		imports = append(imports, i...)
	case *expr.Object:
		for _, nat := range *dt {
			d, i := collect(nat.Attribute)
			data = append(data, d...)
			imports = append(imports, i...)
		}
	case *expr.Array:
		d, i := collect(dt.ElemType)
		data = append(data, d...)
		imports = append(imports, i...)
	case *expr.Map:
		dk, ik := collect(dt.KeyType)
		data = append(data, dk...)
		imports = append(imports, ik...)
		de, ie := collect(dt.ElemType)
		data = append(data, de...)
		imports = append(imports, ie...)
	case *expr.Union:
		for _, nat := range dt.Values {
			d, i := collect(nat.Attribute)
			data = append(data, d...)
			imports = append(imports, i...)
		}
	}
	return data, imports
}

// addValidation adds a validation function (if any) for the given user type
// and recurses through the user type adding other validation functions
// (if any).
//
// req if true indicates that the validation is generated for validating
// request (server-side) messages.
func addValidation(att *expr.AttributeExpr, attName string, sd *ServiceData, req bool) *ValidationData {
	ut, ok := att.Type.(expr.UserType)
	if !ok {
		return nil
	}
	vtx := protoBufTypeContext(sd.PkgName, sd.Scope, false)
	// Validation helper names must be derived from the same protobuf-aware
	// scope used by the validation templates so that function declarations
	// and call sites (e.g. Message_) stay in sync regardless of traversal
	// order or reserved-name handling.
	name := vtx.Scope.Name(att, "", vtx.Pointer, vtx.UseDefault)
	ref := protoBufGoFullTypeRef(att, sd.PkgName, sd.Scope)
	kind := validateClient
	if req {
		kind = validateServer
	}
	att = userTypeAttribute(ut)
	for _, n := range sd.validations {
		if n.SrcName == name {
			if n.Kind != kind {
				n.Kind = validateBoth
				collectValidations(att, attName, req, sd)
			}
			return n
		}
	}
	removeMeta(att)
	if def := codegen.ValidationCode(att, ut, vtx, true, expr.IsAlias(att.Type), false, attName); def != "" {
		v := &ValidationData{
			// Validation function names must match the identifiers used by
			// validation templates. The template uses the scoped type name
			// directly (no Goify) to preserve proto-reserved names like Message_.
			Name:    "Validate" + name,
			Def:     def,
			ArgName: attName,
			SrcName: name,
			SrcRef:  ref,
			Kind:    kind,
		}
		sd.validations = append(sd.validations, v)
		collectValidations(att, attName, req, sd)
		return v
	}
	return nil
}

// collectValidations recurses through the attribute and collects the
// validation functions.
//
// req if true indicates that the validations are generated for validating
// request messages.
func collectValidations(att *expr.AttributeExpr, attName string, req bool, sd *ServiceData) {
	collectValidationsR(att, attName, req, sd, make(map[string]struct{}))
}

// collectValidationsR recurses through the attribute and collects validation
// functions with cycle detection using a seen set of user type IDs.
func collectValidationsR(att *expr.AttributeExpr, attName string, req bool, sd *ServiceData, seen map[string]struct{}) {
	gattName := codegen.Goify(attName, false)
	switch dt := att.Type.(type) {
	case expr.UserType:
		if expr.IsPrimitive(dt) {
			// Alias type - validation is generate inline in parent type validation code.
			return
		}
		// Cycle guard: avoid infinite recursion on recursive user types.
		if id := dt.ID(); id != "" {
			if _, ok := seen[id]; ok {
				return
			}
			seen[id] = struct{}{}
		}
		vtx := protoBufTypeContext(sd.PkgName, sd.Scope, false)
		def := codegen.AttributeValidationCode(att, dt, vtx, true, false, gattName, attName)
		// Match helper function identifiers with validation template calls by
		// using the same protobuf-aware scope for the type name. This keeps
		// names like Message_ consistent between declarations and call sites.
		name := vtx.Scope.Name(att, "", vtx.Pointer, vtx.UseDefault)
		kind := validateClient
		if req {
			kind = validateServer
		}
		for _, n := range sd.validations {
			if n.SrcName == name {
				if n.Kind != validateBoth && n.Kind != kind {
					n.Kind = validateBoth
					goto collect
				}
				return
			}
		}
		if def != "" {
			sd.validations = append(sd.validations, &ValidationData{
				// Match helper function identifiers with validation template
				// calls. The template uses the scoped type name directly (no
				// Goify) to preserve proto-reserved names like Message_.
				Name:    "Validate" + name,
				Def:     def,
				ArgName: gattName,
				SrcName: name,
				SrcRef:  protoBufGoFullTypeRef(att, sd.PkgName, sd.Scope),
				Kind:    kind,
			})
		}
	collect:
		att := userTypeAttribute(dt)
		collectValidationsR(att, attName, req, sd, seen)
	case *expr.Object:
		for _, nat := range *dt {
			collectValidationsR(nat.Attribute, nat.Name, req, sd, seen)
		}
	case *expr.Array:
		collectValidationsR(dt.ElemType, "elem", req, sd, seen)
	case *expr.Map:
		collectValidationsR(dt.KeyType, "key", req, sd, seen)
		collectValidationsR(dt.ElemType, "val", req, sd, seen)
	case *expr.Union:
		for _, nat := range dt.Values {
			collectValidationsR(nat.Attribute, nat.Name, req, sd, seen)
		}
	}
}

// userTypeAttribute returns the attribute of the given user type.
func userTypeAttribute(ut expr.UserType) *expr.AttributeExpr {
	att := ut.Attribute()
	if rt, ok := ut.(*expr.ResultTypeExpr); ok {
		if a := unwrapAttr(expr.DupAtt(rt.Attribute())); expr.IsArray(a.Type) {
			// result type collection
			att = &expr.AttributeExpr{Type: expr.AsObject(rt)}
		}
	}
	return att
}

// buildRequestConvertData builds the convert data for the server and client
// requests.
//   - server side - converts generated gRPC request type in *.pb.go and the
//     gRPC  metadata to method payload type.
//   - client side - converts method payload type to generated gRPC request
//     type in *.pb.go.
//
// svr param indicates that the convert data is generated for server side.
func (d *ServicesData) buildRequestConvertData(request, payload *expr.AttributeExpr, md []*MetadataData, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
	// Server-side: No need to build convert data if payload is empty or payload
	// is not an object type and endpoint streams payload (the payload is
	// encoded in metadata under "goa-payload" in this case).
	if (svr && (isEmpty(payload.Type) || !expr.IsObject(payload.Type) && e.MethodExpr.IsPayloadStreaming())) ||
		// Client-side: No need to build convert data if streaming payload since
		// all attributes in method payload is encoded into request metadata.
		(!svr && e.MethodExpr.IsPayloadStreaming()) {
		return nil
	}

	svc := sd.Service
	pkg := pkgWithDefault(svc.Method(e.MethodExpr.Name).PayloadLoc, svc.PkgName)
	svcCtx := serviceTypeContext(pkg, svc.Scope)
	if svr {
		// server side
		data := d.buildInitData(request, payload, "message", "v", svcCtx, false, svr, false, sd)
		data.Name = fmt.Sprintf("New%sPayload", codegen.Goify(e.Name(), true))
		data.Description = fmt.Sprintf("%s builds the payload of the %q endpoint of the %q service from the gRPC request type.", data.Name, e.Name(), svc.Name)
		for _, m := range md {
			// pass the metadata as arguments to payload constructor in server
			data.Args = append(data.Args, &InitArgData{
				Name:      m.VarName,
				Ref:       m.VarName,
				FieldName: m.FieldName,
				FieldType: m.FieldType,
				TypeName:  m.TypeName,
				TypeRef:   m.TypeRef,
				Type:      m.Type,
				Pointer:   m.Pointer,
				Required:  m.Required,
				Validate:  m.Validate,
				Example:   m.Example,
			})
		}
		return &ConvertData{
			SrcName:    protoBufGoFullTypeName(request, sd.PkgName, sd.Scope),
			SrcRef:     protoBufGoFullTypeRef(request, sd.PkgName, sd.Scope),
			TgtName:    svc.Scope.GoFullTypeName(payload, svcCtx.Pkg(payload)),
			TgtRef:     svc.Scope.GoFullTypeRef(payload, svcCtx.Pkg(payload)),
			Init:       data,
			Validation: addValidation(request, "message", sd, true),
		}
	}

	// client side
	data := d.buildInitData(payload, request, "payload", "message", svcCtx, true, svr, false, sd)
	data.Description = fmt.Sprintf("%s builds the gRPC request type from the payload of the %q endpoint of the %q service.", data.Name, e.Name(), svc.Name)
	return &ConvertData{
		SrcName: svc.Scope.GoFullTypeName(payload, pkg),
		SrcRef:  svc.Scope.GoFullTypeRef(payload, pkg),
		TgtName: protoBufGoFullTypeName(request, sd.PkgName, sd.Scope),
		TgtRef:  protoBufGoFullTypeRef(request, sd.PkgName, sd.Scope),
		Init:    data,
	}
}

// buildResponseConvertData builds the convert data for the server and client
// responses.
//   - server side - converts method result type to generated gRPC response
//     type in *.pb.go
//   - client side - converts generated gRPC response type in *.pb.go and
//     response metadata to method result type.
//
// svr param indicates that the convert data is generated for server side.
func (d *ServicesData) buildResponseConvertData(response, result *expr.AttributeExpr, svcCtx *codegen.AttributeContext, hdrs, trlrs []*MetadataData, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
	if !svr && (e.MethodExpr.IsStreaming() || isEmpty(e.MethodExpr.Result.Type)) {
		return nil
	}
	svc := sd.Service
	if svr {
		// server side
		data := d.buildInitData(result, response, "result", "message", svcCtx, true, svr, false, sd)
		data.Description = fmt.Sprintf("%s builds the gRPC response type from the result of the %q endpoint of the %q service.", data.Name, e.Name(), svc.Name)
		return &ConvertData{
			SrcName: svcCtx.Scope.Name(result, svcCtx.Pkg(result), svcCtx.Pointer, svcCtx.UseDefault),
			SrcRef:  svcCtx.Scope.Ref(result, svcCtx.Pkg(result)),
			TgtName: protoBufGoFullTypeName(response, sd.PkgName, sd.Scope),
			TgtRef:  protoBufGoFullTypeRef(response, sd.PkgName, sd.Scope),
			Init:    data,
		}
	}

	// client side
	data := d.buildInitData(response, result, "message", "result", svcCtx, false, svr, false, sd)
	data.Name = fmt.Sprintf("New%sResult", codegen.Goify(e.Name(), true))
	data.Description = fmt.Sprintf("%s builds the result type of the %q endpoint of the %q service from the gRPC response type.", data.Name, e.Name(), svc.Name)
	for _, m := range hdrs {
		// pass the headers as arguments to result constructor in client
		data.Args = append(data.Args, &InitArgData{
			Name:      m.VarName,
			Ref:       m.VarName,
			FieldName: m.FieldName,
			FieldType: m.FieldType,
			TypeName:  m.TypeName,
			TypeRef:   m.TypeRef,
			Type:      m.Type,
			Pointer:   m.Pointer,
			Required:  m.Required,
			Validate:  m.Validate,
			Example:   m.Example,
		})
	}
	for _, m := range trlrs {
		// pass the trailers as arguments to result constructor in client
		data.Args = append(data.Args, &InitArgData{
			Name:      m.VarName,
			Ref:       m.VarName,
			FieldName: m.FieldName,
			FieldType: m.FieldType,
			TypeName:  m.TypeName,
			TypeRef:   m.TypeRef,
			Type:      m.Type,
			Pointer:   m.Pointer,
			Required:  m.Required,
			Validate:  m.Validate,
			Example:   m.Example,
		})
	}
	return &ConvertData{
		SrcName:    protoBufGoFullTypeName(response, sd.PkgName, sd.Scope),
		SrcRef:     protoBufGoFullTypeRef(response, sd.PkgName, sd.Scope),
		TgtName:    svcCtx.Scope.Name(result, svcCtx.Pkg(result), svcCtx.Pointer, svcCtx.UseDefault),
		TgtRef:     svcCtx.Scope.Ref(result, svcCtx.Pkg(result)),
		Init:       data,
		Validation: addValidation(response, "message", sd, false),
	}
}

// buildInitData builds the transformation code to convert source to target.
//
// source, target are the source and target attributes used in the
// transformation
// sourceVar, targetVar are the source and target variable names used in the
// transformation
// svcCtx is the attribute context for service type
// proto if true indicates the target type is a protocol buffer type
// svr if true indicates the code is generated for conversion server side
func (d *ServicesData) buildInitData(source, target *expr.AttributeExpr, sourceVar, targetVar string, svcCtx *codegen.AttributeContext, proto, _, usesrc bool, sd *ServiceData) *InitData {
	pbCtx := protoBufTypeContext(sd.PkgName, sd.Scope, false)
	name := "New"
	srcCtx := pbCtx
	tgtCtx := svcCtx
	if proto {
		srcCtx = svcCtx
		tgtCtx = pbCtx
		name += "Proto"
	}
	isStruct := expr.IsObject(target.Type) || expr.IsUnion(target.Type)
	if _, ok := source.Type.(expr.UserType); ok && usesrc {
		name += protoBufGoTypeName(source, sd.Scope)
	}
	n := protoBufGoTypeName(target, sd.Scope)
	if !isStruct {
		// If target is array, map, or primitive the name will be suffixed with
		// the definition (e.g int, []string, map[int]string) which is incorrect.
		n = protoBufGoTypeName(source, sd.Scope)
	}
	name += n
	code, helpers, err := protoBufTransform(source, target, sourceVar, targetVar, srcCtx, tgtCtx, proto, true)
	if err != nil {
		panic(err) // bug
	}
	sd.transformHelpers = codegen.AppendHelpers(sd.transformHelpers, helpers)
	var args []*InitArgData
	if (!proto && !isEmpty(source.Type)) || (proto && !isEmpty(target.Type)) {
		args = []*InitArgData{{
			Name:     sourceVar,
			Ref:      sourceVar,
			TypeName: srcCtx.Scope.Name(source, srcCtx.Pkg(source), srcCtx.Pointer, srcCtx.UseDefault),
			TypeRef:  srcCtx.Scope.Ref(source, srcCtx.Pkg(source)),
			Example:  source.Example(d.Root.API.ExampleGenerator),
		}}
	}
	return &InitData{
		Name:           name,
		ReturnVarName:  targetVar,
		ReturnTypeRef:  tgtCtx.Scope.Ref(target, tgtCtx.Pkg(target)),
		ReturnIsStruct: isStruct,
		ReturnTypePkg:  tgtCtx.Pkg(target),
		Code:           code,
		Args:           args,
	}
}

// buildErrorsData builds the error data for all the error responses in the
// endpoint expression. The response message for each error response are
// inferred from the method's error expression if not specified explicitly.
func (d *ServicesData) buildErrorsData(e *expr.GRPCEndpointExpr, sd *ServiceData) []*ErrorData {
	svc := sd.Service
	errors := make([]*ErrorData, 0, len(e.GRPCErrors))
	for _, v := range e.GRPCErrors {
		responseData := &ResponseData{
			StatusCode:    statusCodeToGRPCConst(v.Response.StatusCode),
			Description:   v.Response.Description,
			ServerConvert: d.buildErrorConvertData(v, e, sd, true),
			ClientConvert: d.buildErrorConvertData(v, e, sd, false),
		}
		errorLoc := svc.Method(e.MethodExpr.Name).ErrorLocs[v.Name]
		errors = append(errors, &ErrorData{
			Name:     v.Name,
			Ref:      svc.Scope.GoFullTypeRef(v.AttributeExpr, pkgWithDefault(errorLoc, svc.PkgName)),
			Response: responseData,
		})
	}
	return errors
}

func (d *ServicesData) buildErrorConvertData(ge *expr.GRPCErrorExpr, e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *ConvertData {
	// No need to build transformation functions for default error or non-object
	// types.
	if ge.Type == expr.ErrorResult || !expr.IsObject(ge.Type) {
		return nil
	}
	svc := sd.Service
	svcCtx := serviceTypeContext(svc.PkgName, svc.Scope)
	if svr {
		// server side
		data := d.buildInitData(ge.AttributeExpr, ge.Response.Message, "er", "message", svcCtx, true, svr, false, sd)
		data.Name = fmt.Sprintf("New%s%sError", codegen.Goify(e.Name(), true), codegen.Goify(ge.Name, true))
		data.Description = fmt.Sprintf("%s builds the gRPC error response type from the error of the %q endpoint of the %q service.", data.Name, e.Name(), svc.Name)
		return &ConvertData{
			SrcName: svcCtx.Scope.Name(ge.AttributeExpr, svcCtx.Pkg(ge.AttributeExpr), svcCtx.Pointer, svcCtx.UseDefault),
			SrcRef:  svcCtx.Scope.Ref(ge.AttributeExpr, svcCtx.Pkg(ge.AttributeExpr)),
			TgtName: protoBufGoFullTypeName(ge.Response.Message, sd.PkgName, sd.Scope),
			TgtRef:  protoBufGoFullTypeRef(ge.Response.Message, sd.PkgName, sd.Scope),
			Init:    data,
		}
	}

	// client side
	data := d.buildInitData(ge.Response.Message, ge.AttributeExpr, "message", "er", svcCtx, false, svr, false, sd)
	data.Name = fmt.Sprintf("New%s%sError", codegen.Goify(e.Name(), true), codegen.Goify(ge.Name, true))
	data.Description = fmt.Sprintf("%s builds the error type of the %q endpoint of the %q service from the gRPC error response type.", data.Name, e.Name(), svc.Name)
	return &ConvertData{
		SrcName:    protoBufGoFullTypeName(ge.Response.Message, sd.PkgName, sd.Scope),
		SrcRef:     protoBufGoFullTypeRef(ge.Response.Message, sd.PkgName, sd.Scope),
		TgtName:    svcCtx.Scope.Name(ge.AttributeExpr, svcCtx.Pkg(ge.AttributeExpr), svcCtx.Pointer, svcCtx.UseDefault),
		TgtRef:     svcCtx.Scope.Ref(ge.AttributeExpr, svcCtx.Pkg(ge.AttributeExpr)),
		Init:       data,
		Validation: addValidation(ge.Response.Message, "errmsg", sd, false),
	}
}

// buildStreamData builds the StreamData for the server and client streams.
//
// svr param indicates that the stream data is built for the server.
func (d *ServicesData) buildStreamData(e *expr.GRPCEndpointExpr, sd *ServiceData, svr bool) *StreamData {
	var (
		varn                string
		intName             string
		svcInt              string
		sendName            string
		sendDesc            string
		sendWithContextName string
		sendWithContextDesc string
		sendRef             string
		sendConvert         *ConvertData
		recvName            string
		recvDesc            string
		recvWithContextName string
		recvWithContextDesc string
		recvRef             string
		recvConvert         *ConvertData
		mustClose           bool
		typ                 string
	)
	svc := sd.Service
	ed := sd.Endpoint(e.Name())
	md := ed.Method
	svcCtx := serviceTypeContext(svc.PkgName, svc.Scope)
	result, resCtx := resultContext(e, sd)
	resVar := "result"
	if md.ViewedResult != nil {
		resVar = "vresult"
	}
	if svr {
		typ = "server"
		varn = md.ServerStream.VarName
		intName = fmt.Sprintf("%s.%s_%sServer", sd.PkgName, svc.StructName, md.VarName)
		svcInt = fmt.Sprintf("%s.%s", svc.PkgName, md.ServerStream.Interface)
		if e.MethodExpr.Result.Type != expr.Empty {
			sendName = md.ServerStream.SendName
			sendRef = ed.ResultRef
			sendWithContextName = md.ServerStream.SendWithContextName
			sendConvert = &ConvertData{
				SrcName: resCtx.Scope.Name(result, resCtx.Pkg(result), resCtx.Pointer, resCtx.UseDefault),
				SrcRef:  resCtx.Scope.Ref(result, resCtx.Pkg(result)),
				TgtName: protoBufGoFullTypeName(e.Response.Message, sd.PkgName, sd.Scope),
				TgtRef:  protoBufGoFullTypeRef(e.Response.Message, sd.PkgName, sd.Scope),
				Init:    d.buildInitData(result, e.Response.Message, resVar, "v", resCtx, true, svr, true, sd),
			}
		}
		if e.MethodExpr.StreamingPayload.Type != expr.Empty {
			recvName = md.ServerStream.RecvName
			recvWithContextName = md.ServerStream.RecvWithContextName
			recvRef = svcCtx.Scope.Ref(e.MethodExpr.StreamingPayload, svcCtx.Pkg(e.MethodExpr.StreamingPayload))
			recvConvert = &ConvertData{
				SrcName:    protoBufGoFullTypeName(e.StreamingRequest, sd.PkgName, sd.Scope),
				SrcRef:     protoBufGoFullTypeRef(e.StreamingRequest, sd.PkgName, sd.Scope),
				TgtName:    svcCtx.Scope.Name(e.MethodExpr.StreamingPayload, svcCtx.Pkg(e.MethodExpr.StreamingPayload), svcCtx.Pointer, svcCtx.UseDefault),
				TgtRef:     recvRef,
				Init:       d.buildInitData(e.StreamingRequest, e.MethodExpr.StreamingPayload, "v", "spayload", svcCtx, false, svr, true, sd),
				Validation: addValidation(e.StreamingRequest, "stream", sd, true),
			}
		}
		mustClose = md.ServerStream.MustClose
	} else {
		typ = "client"
		varn = md.ClientStream.VarName
		intName = fmt.Sprintf("%s.%s_%sClient", sd.PkgName, svc.StructName, md.VarName)
		svcInt = fmt.Sprintf("%s.%s", svc.PkgName, md.ClientStream.Interface)
		if e.MethodExpr.StreamingPayload.Type != expr.Empty {
			sendName = md.ClientStream.SendName
			sendWithContextName = md.ClientStream.SendWithContextName
			sendRef = svcCtx.Scope.Ref(e.MethodExpr.StreamingPayload, svcCtx.Pkg(e.MethodExpr.StreamingPayload))
			sendConvert = &ConvertData{
				SrcName: svcCtx.Scope.Name(e.MethodExpr.StreamingPayload, svcCtx.Pkg(e.MethodExpr.StreamingPayload), svcCtx.Pointer, svcCtx.UseDefault),
				SrcRef:  sendRef,
				TgtName: protoBufGoFullTypeName(e.StreamingRequest, sd.PkgName, sd.Scope),
				TgtRef:  protoBufGoFullTypeRef(e.StreamingRequest, sd.PkgName, sd.Scope),
				Init:    d.buildInitData(e.MethodExpr.StreamingPayload, e.StreamingRequest, "spayload", "v", svcCtx, true, svr, true, sd),
			}
		}
		if e.MethodExpr.Result.Type != expr.Empty {
			recvName = md.ClientStream.RecvName
			recvWithContextName = md.ClientStream.RecvWithContextName
			recvRef = ed.ResultRef
			recvConvert = &ConvertData{
				SrcName:    protoBufGoFullTypeName(e.Response.Message, sd.PkgName, sd.Scope),
				SrcRef:     protoBufGoFullTypeRef(e.Response.Message, sd.PkgName, sd.Scope),
				TgtName:    resCtx.Scope.Name(result, resCtx.Pkg(result), resCtx.Pointer, resCtx.UseDefault),
				TgtRef:     resCtx.Scope.Ref(result, resCtx.Pkg(result)),
				Init:       d.buildInitData(e.Response.Message, result, "v", resVar, resCtx, false, svr, true, sd),
				Validation: addValidation(e.Response.Message, "stream", sd, false),
			}
		}
		mustClose = md.ClientStream.MustClose
	}
	if sendConvert != nil {
		sendDesc = fmt.Sprintf("%s streams instances of %q to the %q endpoint gRPC stream.", sendName, sendConvert.TgtName, md.Name)
		sendWithContextDesc = fmt.Sprintf("%s streams instances of %q to the %q endpoint gRPC stream with context.", sendWithContextName, sendConvert.TgtName, md.Name)
	}
	if recvConvert != nil {
		recvDesc = fmt.Sprintf("%s reads instances of %q from the %q endpoint gRPC stream.", recvName, recvConvert.SrcName, md.Name)
		recvWithContextDesc = fmt.Sprintf("%s reads instances of %q from the %q endpoint gRPC stream with context.", recvWithContextName, recvConvert.SrcName, md.Name)
	}
	return &StreamData{
		VarName:             varn,
		Type:                typ,
		Interface:           intName,
		ServiceInterface:    svcInt,
		Endpoint:            ed,
		SendName:            sendName,
		SendDesc:            sendDesc,
		SendWithContextName: sendWithContextName,
		SendWithContextDesc: sendWithContextDesc,
		SendRef:             sendRef,
		SendConvert:         sendConvert,
		RecvName:            recvName,
		RecvDesc:            recvDesc,
		RecvWithContextName: recvWithContextName,
		RecvWithContextDesc: recvWithContextDesc,
		RecvRef:             recvRef,
		RecvConvert:         recvConvert,
		MustClose:           mustClose,
	}
}

// extractMetadata collects the request/response metadata from the given
// metadata attribute and service type (payload/result).
func extractMetadata(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, scope *codegen.NameScope, services ServicesData) []*MetadataData {
	var metadata []*MetadataData
	ctx := serviceTypeContext("", scope)
	codegen.WalkMappedAttr(a, func(name, elem string, required bool, c *expr.AttributeExpr) error { // nolint: errcheck
		arr := expr.AsArray(c.Type)
		mp := expr.AsMap(c.Type)
		typeRef := scope.GoTypeRef(unalias(c))
		ft := service.Type
		varn := scope.Name(codegen.Goify(name, false))
		fieldName := codegen.Goify(name, true)
		var pointer bool
		if !expr.IsObject(service.Type) {
			fieldName = ""
		} else {
			pointer = service.IsPrimitivePointer(name, true)
			ft = service.Find(name).Type
		}
		if pointer {
			typeRef = "*" + typeRef
		}
		metadata = append(metadata, &MetadataData{
			Name:          elem,
			AttributeName: name,
			Description:   c.Description,
			FieldName:     fieldName,
			FieldType:     ft,
			VarName:       varn,
			Required:      required,
			Type:          c.Type,
			TypeName:      scope.GoTypeName(unalias(c)),
			TypeRef:       typeRef,
			Pointer:       pointer,
			Slice:         arr != nil,
			StringSlice:   arr != nil && arr.ElemType.Type.Kind() == expr.StringKind,
			Map:           mp != nil,
			MapStringSlice: mp != nil &&
				mp.KeyType.Type.Kind() == expr.StringKind &&
				mp.ElemType.Type.Kind() == expr.ArrayKind &&
				expr.AsArray(mp.ElemType.Type).ElemType.Type.Kind() == expr.StringKind,
			Validate:     codegen.AttributeValidationCode(c, nil, ctx, required, false, varn, name),
			DefaultValue: c.DefaultValue,
			Example:      c.Example(services.Root.API.ExampleGenerator),
		})
		return nil
	})
	return metadata
}

func unalias(att *expr.AttributeExpr) *expr.AttributeExpr {
	if ut, ok := att.Type.(expr.UserType); ok {
		if _, ok := ut.Attribute().Type.(expr.Primitive); ok {
			return ut.Attribute()
		}
		return unalias(ut.Attribute())
	}
	return att
}

// serviceTypeContext returns a contextual attribute for service types. Service
// types are Go types and uses non-pointers to hold attributes having default
// values.
func serviceTypeContext(pkg string, scope *codegen.NameScope) *codegen.AttributeContext {
	return codegen.NewAttributeContext(false, false, true, pkg, scope)
}

// resultContext returns the method result attribute and the result context for the given
// endpoint.
func resultContext(e *expr.GRPCEndpointExpr, sd *ServiceData) (*expr.AttributeExpr, *codegen.AttributeContext) {
	svc := sd.Service
	md := svc.Method(e.Name())
	if md.ViewedResult != nil {
		vresAtt := expr.AsObject(md.ViewedResult.Type).Attribute("projected")
		// return projected type context
		return vresAtt, codegen.NewAttributeContext(true, false, true, svc.ViewsPkg, svc.ViewScope)
	}
	pkg := pkgWithDefault(md.ResultLoc, svc.PkgName)
	return e.MethodExpr.Result, serviceTypeContext(pkg, svc.Scope)
}

// pkgWithDefault returns the package name of the given location if not nil, def otherwise.
func pkgWithDefault(loc *codegen.Location, def string) string {
	if loc == nil {
		return def
	}
	return loc.PackageName()
}

// getPrimitive returns the primitive expression if the given expression is an alias to one
func getPrimitive(att *expr.AttributeExpr) *expr.AttributeExpr {
	if ut, ok := att.Type.(*expr.UserTypeExpr); ok {
		if _, ok := ut.Type.(expr.Primitive); ok {
			return ut.AttributeExpr
		}
		return getPrimitive(ut.AttributeExpr)
	}
	return nil
}

// isEmpty returns true if given type is empty.
func isEmpty(dt expr.DataType) bool {
	if dt == expr.Empty {
		return true
	}
	if o := expr.AsObject(dt); o != nil && len(*o) == 0 {
		return true
	}
	return false
}

// hasAnyType recursively checks if the given attribute uses the Any type.
func hasAnyType(att *expr.AttributeExpr) bool {
	if att == nil {
		return false
	}
	if att.Type.Kind() == expr.AnyKind {
		return true
	}
	switch dt := att.Type.(type) {
	case expr.UserType:
		return hasAnyType(dt.Attribute())
	case *expr.Object:
		for _, nat := range *dt {
			if hasAnyType(nat.Attribute) {
				return true
			}
		}
	case *expr.Array:
		return hasAnyType(dt.ElemType)
	case *expr.Map:
		return hasAnyType(dt.KeyType) || hasAnyType(dt.ElemType)
	case *expr.Union:
		for _, nat := range dt.Values {
			if hasAnyType(nat.Attribute) {
				return true
			}
		}
	}
	return false
}
